******************************************************************* Accessing nested OLE object properties with TOLEAutoClient ******************************************************************* ********************************* NESTED MIRROR CLASSES ********************************* In cases when an OLE Automation server exposes "nested" objects, such as in the case of Microsoft Excel and Microsoft Project and many others, the "mirror" class should be written to contain, as a member, an instance of the sub-object's mirror class. Refer to the example and sample code in the accompanying file GRAPHOLE.ZIP for a simple illustration of the approach, using Microsoft's MSGRAPH as the server. Other accompanying example files also give additional illustrative material for reference. Before any properties or methods of the sub-object can be accessed, it will be necessary to execute code to construct the sub-object instance, as described below. For additional information and examples, refer to the zipped example files supplied. You will need to consult your server's documentation for information on the class hierarchy exposed by your server. For Microsoft applications, the Developer Network CD-ROM is a useful source of such information. For example, suppose a server whose ID is "Myserver.Document" exposes one string property called Name, and also contains a sub-object called Font which in turn has a string property called Color. In this case, the "mirror" classes might look like this: interface uses SysUtils, OleAuto; type TDocFont = class(TOleObject) private function GetColor : String; procedure SetColor(str : String); public property Color : String read GetColor write SetColor; end; TDocument = class(TOleObject) private fontFont : TDocFont; function GetName : String; procedure SetName(sName : String); function GetFont : TDocFont; public property Name : String read GetName write SetName; property DocFont : TDocFont read GetFont write fontFont; end; implementation function TDocument.GetName : String; const pszName : PChar = 'naaaaaammmmmmmme'; begin GetOleProperty('Name', 'PChar', pszName); Result := StrPas(pszName); end; procedure TDocument.SetName(sName : String); const pszName : PChar = 'naaaaaammmmmmmme'; begin StrPCopy(pszName, sName); SetOleProperty('Name', 'PChar', pszName); end; function TDocument.GetFont : TDocFont; var pIntFont : PInterface; begin GetOleProperty('Font', 'pInterface', pIntFont); fontFont := TDocFont.ConnectInterface(pIntFont); Result := fontFont; end; function TDocFont.GetColor : String; const pszColor : PChar = 'collllllooooooooor'; begin GetOleProperty('Color', 'PChar', sColor); Result := StrPas(pszColor); end; procedure TDocFont.SetColor(sColor : String); const pszColor : PChar = 'collllllooooooooor'; begin StrPCopy(pszColor, sColor); SetOleProperty('Color', 'PChar', pszColor); end; Pay particular attention to the declaration and implementation of the DocFont property. The property is represented as a private class member (field) of type TDocFont. Setting the property (its write access) is accomplished merely by assigning to it, but retrieving the property (its read access) is accomplished through the TDocument.GetFont function. This function has the essential side-effect of calling the TDocFont constructor ConnectInterface, inherited from TOleObject. This is because before any properties of the DocFont sub-object can be accessed, it must be constructed using a call to its ConnectInterface constructor, which takes as its argument the pInterface returned by the call to GetOleProperty that retrieves the pInterface for the OLE server's DocFont property. For some servers, you may need to retrieve the pInterface for a sub-property or collection by using CallOleFunction instead of GetOleProperty. Also, the 'pInterface' type defined for this component may be referred to as 'pDisp', 'LPDISPATCH', 'LPDISP', or some similar type in your server documentation. All are equivalent to a pointer to a low-level OLE IDispatch interface. In order to access the OLE server properties, you first need to activate the server by calling its mirror class CreateObject (or GetObject or GetInProcessObject) constructor. Then you can access the properties in the normal way. A typical code fragment might look like: var sMyName, sMyColor : String; Document1 : TDocument; begin Document1 := TDocument.CreateObject('myserver.document'); sMyName := Document1.Name; sMyColor := Document1.Font.Color; Document1.Release; ... end; ********************************* MEMORY MANAGEMENT - AVOID LEAKAGE ********************************* Several memory management issues arise when writing mirror classes. First of all, for simplicity the above example uses Delphi "typed constants" to hold local string variables. Despite the name given to them by Object Pascal (i.e., Delphi), these are not constants at all, but really static variables to which values can be assigned. While there is nothing really wrong with using them for this purpose, it is not really good programming practice, since they are allocated in the application's data segment and thus consume valuable limited resources. A better approach would be to use dynamic allocation via, for example, the StrAlloc() function. Of course, each string allocated in this way must be freed using the StrDispose procedure. Using this approach, the first function in the above example could be rewritten as: function TDocument.GetName : String; var pszName : PChar; begin pszName := StrAlloc(256); { Get some space. } GetOleProperty('Name', 'PChar', pszName); Result := StrPas(pszName); StrDispose(pszName); { Free the no-longer-needed space. } end; Note that, to be absolutely safe, you could protect the memory allocation within an exception-handling block. A more subtle memory allocation issue arises when writing mirror classes for nested objects. Within the mirror class, when you call a nested class ConnectInterface constructor, Delphi allocates memory dynamically on the heap for the members of the instance of that class that it creates. Unless you explicitly keep track of such instances, and call their destructors, "memory leaks" within Windows can occur when your application shuts down. Delphi's internal memory allocator can sometimes protect you against this when only a few such constructor calls are made, but not if they are called repeatedly. The best practice is to keep track of these yourself within your mirror class. Fortunately, this is easy to do since code has already been written for you. One way of doing this is to write a descendant (or subclass) of TOleObject that overrides the ConnectInterface constructor in order to keep track of the relevant object references in a list. See the code in VISIOOLE.ZIP for an example.